package sls

import (
	"codeup.aliyun.com/5f9118049cffa29cfdd3be1c/util/mapstructure"
	"encoding/json"
	"fmt"
	sls "github.com/aliyun/aliyun-log-go-sdk"
	"google.golang.org/protobuf/proto"
	"strings"
	"time"
)

type Client struct {
	client      sls.ClientInterface
	projectName string
	logStore    string
	topic       string
}

type GetLogsWithCountResponse struct {
	Logs       []interface{}
	TotalCount int64
}

func NewSlsClient(config *Config) (*Client, error) {
	if !config.IsSetRequiredFields() {
		return nil, fmt.Errorf("config is not set")
	}
	client := sls.CreateNormalInterfaceV2(config.Endpoint, sls.NewStaticCredentialsProvider(config.AccessKeyId, config.AccessKeySecret, ""))
	return &Client{
		client:      client,
		projectName: DefaultProjectName,
		logStore:    DefaultLogStore,
		topic:       DefaultLogTopic,
	}, nil
}

func (c *Client) SetProject(projectName string) *Client {
	c.projectName = projectName
	return c
}

func (c *Client) SetLogStore(logStore string) *Client {
	c.logStore = logStore
	return c
}

func (c *Client) SetTopic(topic string) *Client {
	c.topic = topic
	return c
}

func (c *Client) PushLogs(logs []interface{}) error {
	lg, err := c.convertLogs(logs)
	if err != nil {
		return fmt.Errorf("convertLogs error : %v", err)
	}
	err = c.client.PutLogs(c.projectName, c.logStore, lg)
	if err != nil {
		return fmt.Errorf("PutLogs error : %v", err)
	}
	return nil
}

func (c *Client) GetLogs(req *GetLogRequest) ([]interface{}, error) {
	in := &sls.GetLogRequest{}
	_ = mapstructure.Decode(req, in)
	if in.Query == "" {
		in.Query = c.buildGetLogRequestQuery(req.QueryMap)
	}
	response, err := c.client.GetLogsV3(c.projectName, c.logStore, in)
	if err != nil {
		return nil, fmt.Errorf("GetLogs error : %v", err)
	}
	res := make([]interface{}, len(response.Logs))
	for k, v := range response.Logs {
		if data, ok := v[LogDataFieldName]; ok && data != "" {
			err := json.Unmarshal([]byte(data), &res[k])
			if err != nil {
				return nil, err
			}
		}
	}
	return res, nil
}

func (c *Client) GetLogsWithCount(req *GetLogRequest) (*GetLogsWithCountResponse, error) {
	in := &sls.GetLogRequest{}
	_ = mapstructure.Decode(req, in)
	if in.Query == "" {
		in.Query = c.buildGetLogRequestQuery(req.QueryMap)
	}
	response, err := c.client.GetLogsV3(c.projectName, c.logStore, in)
	if err != nil {
		return nil, fmt.Errorf("GetLogs error : %v", err)
	}
	res := make([]interface{}, len(response.Logs))
	for k, v := range response.Logs {
		if data, ok := v[LogDataFieldName]; ok && data != "" {
			err := json.Unmarshal([]byte(data), &res[k])
			if err != nil {
				return nil, err
			}
		}
	}

	return &GetLogsWithCountResponse{
		Logs:       res,
		TotalCount: response.Meta.Count,
	}, nil
}

func (c *Client) convertLogs(logs []interface{}) (*sls.LogGroup, error) {
	if len(logs) == 0 {
		return nil, fmt.Errorf("logs is empty")
	}
	nowTime := time.Now()
	logsEntity := make([]*sls.Log, len(logs))
	for k, v := range logs {
		contents := make([]*sls.LogContent, 0)
		logsEntity[k] = &sls.Log{
			Time:     proto.Uint32(uint32(nowTime.Unix())),
			Contents: []*sls.LogContent{},
			TimeNs:   proto.Uint32(uint32(nowTime.Nanosecond())),
		}
		bytes, err := json.Marshal(v)
		if err != nil {
			return nil, err
		}
		contents = append(contents, &sls.LogContent{
			Key:   proto.String(LogDataFieldName),
			Value: proto.String(string(bytes)),
		})
		logsEntity[k].Contents = contents
	}
	logGroup := &sls.LogGroup{
		Logs:  logsEntity,
		Topic: proto.String(c.topic),
	}
	return logGroup, nil
}

func (c *Client) buildGetLogRequestQuery(queryMap map[string]interface{}) string {
	var builder strings.Builder
	builder.WriteString("__topic__: ")
	builder.WriteString(c.topic)
	for k, v := range queryMap {
		builder.WriteString(" and ")
		builder.WriteString(LogDataFieldName)
		builder.WriteString(".")
		builder.WriteString(k)
		builder.WriteString(": ")
		builder.WriteString(fmt.Sprintf("%v", v))
	}
	return builder.String()
}

func (c *Client) Close() error {
	return c.client.Close()
}
